<template>
  <div style="position: absolute">
    <div class="contain" :style="getContain">
      <div class="wrap-box" :style="getStyle(imgData.canvas)">
        <div class="canvas">
          <img :src="imgUrl" :style="{width: imgData.canvas.width + 'px', height: imgData.canvas.height + 'px'}" class="canvas-hide"/>
        </div>
      </div>
      <div class="drag-box move modal" data-cropper-action="move" :style="getStyle(imgData.canvas)"></div>
      <div class="crop-box" :style="getStyle(imgData.cropBox)" @mousedown="handleDown">
          <span class="view-box" @mousedown="handleCanvasDown">
            <img :src="imgUrl" :style="{...getStyle(imgData.cropBoxImg), width: imgData.canvas.width + 'px', height: imgData.canvas.height + 'px'}" alt="The image to preview">
          </span>
        <span class="dashed dashed-h"></span>
        <span class="dashed dashed-v"></span>
        <span class="face move" data-cropper-action="move"></span>
        <span class="line line-e" data-cropper-action="e"></span>
        <span class="line line-n" data-cropper-action="n"></span>
        <span class="line line-w" data-cropper-action="w"></span>
        <span class="line line-s" data-cropper-action="s"></span>
        <span class="point point-e" data-cropper-action="e"></span>
        <span class="point point-n" data-cropper-action="n"></span>
        <span class="point point-w" data-cropper-action="w"></span>
        <span class="point point-s" data-cropper-action="s"></span>
        <span class="point point-ne" data-cropper-action="ne"></span>
        <span class="point point-nw" data-cropper-action="nw"></span>
        <span class="point point-sw" data-cropper-action="sw"></span>
        <span class="point point-se" data-cropper-action="se"></span>
      </div>
    </div>
    <div class="tooltip" v-show="tooltipState" :style="{top: tooltipStyle.top + 'px', left: tooltipStyle.left + 'px'}">
      <span class="tooltip-btn" @click="handleCropper(false)">取消</span>
      <span class="tooltip-btn" style="color: #20a0ff" @click="handleCropper(true)">完成</span>
    </div>
  </div>

</template>

<script>
export default {
  name: 'Cropper',
  props: {
    parentId: {
      type: String,
      default: 'edit'
    },
    imgUrl: {
      type: String,
      default: 'http://localhost:9528/static/img/1.e256e151.jpg'
    },
    cropperInfo: {
      type: Object,
      default: () => {
        return {
          left: 0,
          top: 0,
          width: 0,
          height: 0,
          rotate: 0
        }
      }
    },
    imgInfo: {
      type: Object,
      default: () => {
        return {
          left: 0,
          top: 0,
          width: 0,
          height: 0,
          scaleX: 1,
          scaleY: 1,
          rotate: 0
        }
      }
    }
  },
  data() {
    return {
      rotate: 0,
      imgData: {
        contain: {
          width: 0,
          height: 0,
          left: 0,
          top: 0
        },
        canvas: {
          left: 0,
          top: 0,
          width: 0,
          height: 0
        },
        cropBox: {
          left: 0,
          top: 0,
          width: 0,
          height: 0
        },
        cropBoxImg: {
          left: 0,
          top: 0
        }
      },
      tooltipStyle: {
        top: -40,
        left: 0
      },
      tooltipState: true
    }
  },
  computed: {
    getContain() {
      return {
        ...this.getStyle(this.imgData.contain),
        top: this.imgData.contain.top + 'px',
        left: this.imgData.contain.left + 'px',
        transform: `rotate(${this.rotate || 0}deg)`
      }
    }
  },
  mounted() {
    this.setImgData()
  },
  methods: {
    handleCropper(state) {
      let newCropperInfo = {
        left: this.cropperInfo.left,
        top: this.cropperInfo.top,
        width: this.cropperInfo.width,
        height: this.cropperInfo.height,
        rotate: this.cropperInfo.rotate,
        imgInfo: {
          left: this.imgInfo.left,
          top: this.imgInfo.top,
          width: this.imgInfo.width,
          height: this.imgInfo.height,
          scaleX: this.imgInfo.scaleX,
          scaleY: this.imgInfo.scaleY
        }
      }
      if (state) {
        const newCoordinate = this.getTransformPoint()
        newCropperInfo = {
          left: newCoordinate.x,
          top: newCoordinate.y,
          width: this.imgData.cropBox.width,
          height: this.imgData.cropBox.height,
          rotate: this.cropperInfo.rotate,
          imgInfo: {
            left: this.imgData.cropBoxImg.left,
            top: this.imgData.cropBoxImg.top,
            width: this.imgData.canvas.width / this.imgInfo.scaleX,
            height: this.imgData.canvas.height / this.imgInfo.scaleY,
            scaleX: this.imgInfo.scaleX,
            scaleY: this.imgInfo.scaleY
          }
        }
      }
      this.$emit('handleCropper', newCropperInfo)
    },
    /**
     * 获取获取旋转变换后的left,top
     * */
    getTransformPoint() {
      const newLeftPoint = this.getCenter(
        { x: this.cropperInfo.left + this.imgData.cropBox.left, y: this.cropperInfo.top + this.imgData.cropBox.top },
        { x: this.cropperInfo.left + this.cropperInfo.width / 2, y: this.cropperInfo.top + this.cropperInfo.height / 2 },
        this.cropperInfo.rotate
      )
      const center = this.getCenter(
        { x: this.cropperInfo.left + this.imgData.cropBox.left + this.imgData.cropBox.width / 2, y: this.cropperInfo.top + this.imgData.cropBox.top + this.imgData.cropBox.height / 2 },
        { x: this.cropperInfo.left + this.cropperInfo.width / 2, y: this.cropperInfo.top + this.cropperInfo.height / 2 },
        this.cropperInfo.rotate)
      const newCoordinate = this.getCenter(
        { x: newLeftPoint.x, y: newLeftPoint.y },
        { x: center.x, y: center.y },
        -this.cropperInfo.rotate
      )
      return newCoordinate
    },
    getCenter(point, center, rotate) {
      const deg = Math.PI / 180 * rotate
      const newX = center.x + (point.x - center.x) * Math.cos(deg) - (point.y - center.y) * Math.sin(deg)
      const newY = center.y + (point.x - center.x) * Math.sin(deg) + (point.y - center.y) * Math.cos(deg)
      return {
        x: newX,
        y: newY
      }
    },
    getStyle(styleObj) {
      const needUnitStr = ['width', 'height']
      const style = {}
      for (const key in styleObj) {
        if (needUnitStr.includes(key)) {
          style[key] = styleObj[key] + 'px'
        } else if (key !== 'left' && key !== 'top') {
          style[key] = styleObj[key]
        }
      }
      style.transform = `translate(${styleObj.left}px, ${styleObj.top}px)`
      return style
    },
    setImgData() {
      this.imgData.contain = {
        width: this.cropperInfo.width,
        height: this.cropperInfo.height,
        left: this.cropperInfo.left,
        top: this.cropperInfo.top
      }
      this.imgData.canvas = {
        width: this.imgInfo.width * this.imgInfo.scaleX,
        height: this.imgInfo.height * this.imgInfo.scaleY,
        left: this.imgInfo.left,
        top: this.imgInfo.top
      }
      this.imgData.cropBox = {
        width: this.cropperInfo.width,
        height: this.cropperInfo.height,
        top: 0,
        left: 0
      }
      this.imgData.cropBoxImg = {
        top: this.imgInfo.top,
        left: this.imgInfo.left
      }
      this.rotate = this.cropperInfo.rotate || 0
      this.changeTooltip(this.rotate)
    },
    /**
     * 用于修改tooltip 位置
     * */
    changeTooltip(rotate) {
      const point = this.getTransformPoint()
      const newPoint = this.transform({
        x: point.x,
        y: point.y,
        width: this.imgData.cropBox.width,
        height: this.imgData.cropBox.height
      }, rotate)
      if (newPoint.top >= 40) {
        this.tooltipStyle = {
          left: newPoint.left,
          top: newPoint.top - 40
        }
      } else {
        this.tooltipStyle = {
          left: newPoint.left,
          top: newPoint.bottom + 10
        }
      }
      this.tooltipState = true
    },
    handleCanvasDown(e) {
      this.tooltipState = false
      const canvas = { ...this.imgData.canvas }
      const { top, left } = canvas
      const { top: cTop, left: cLeft } = this.imgData.cropBox
      const parentPosition = document.getElementById(this.parentId).getBoundingClientRect()
      const ex = e.pageX - parentPosition.x
      const ey = e.pageY - parentPosition.y
      const move = moveE => {
        moveE.stopPropagation()
        moveE.preventDefault()
        const currX = moveE.pageX - parentPosition.x
        const currY = moveE.pageY - parentPosition.y
        const skewVal = Math.sqrt(Math.pow(currX - ex, 2) + Math.pow(currY - ey, 2))
        const deg = Math.acos((currX - ex) / skewVal) * 180 / Math.PI
        if (currY - ey > 0) {
          canvas.top = skewVal * Math.sin((deg - this.rotate) * Math.PI / 180) + top
          canvas.left = skewVal * Math.cos((deg - this.rotate) * Math.PI / 180) + left
        } else {
          canvas.top = -skewVal * Math.sin((deg + this.rotate) * Math.PI / 180) + top
          canvas.left = skewVal * Math.cos((deg + this.rotate) * Math.PI / 180) + left
        }
        this.imgData.canvas = {
          ...canvas
        }
        const newILeft = -cLeft + canvas.left
        const newITop = -cTop + canvas.top
        this.imgData.cropBoxImg = {
          left: newILeft,
          top: newITop
        }
      }
      const up = () => {
        const { left, top, width, height } = this.imgData.cropBox
        const {
          left: cLeft,
          top: cTop,
          width: cWidth,
          height: cHeight
        } = this.imgData.canvas
        let newLeft = left >= cLeft ? cLeft : left
        let newTop = top >= cTop ? cTop : top
        newLeft = cLeft + cWidth >= left + width ? newLeft : left + width - cWidth
        newTop = cTop + cHeight >= top + height ? newTop : top + height - cHeight
        this.imgData.canvas.left = newLeft
        this.imgData.canvas.top = newTop
        this.imgData.cropBoxImg.left = newLeft - this.imgData.cropBox.left
        this.imgData.cropBoxImg.top = newTop - this.imgData.cropBox.top
        this.changeTooltip(this.rotate)
        document.removeEventListener('mousemove', move, true)
        document.removeEventListener('mouseup', up, true)
      }
      document.addEventListener('mousemove', move, true)
      document.addEventListener('mouseup', up, true)
      return true
    },
    handleDown(e) {
      this.tooltipState = false
      e.preventDefault()
      const cropperAction = e.target.dataset.cropperAction
      if (cropperAction && cropperAction !== 'move') {
        const cropBox = {
          ...this.imgData.cropBox
        }
        const { left, top, width, height } = cropBox
        const { left: cLeft, top: cTop } = this.imgData.canvas
        const parentPosition = document.getElementById(this.parentId).getBoundingClientRect()
        const ex = e.pageX - parentPosition.x
        const ey = e.pageY - parentPosition.y
        const oldPoint = {
          left,
          top,
          width,
          height
        }
        const move = (moveE) => {
          const moveX = moveE.pageX - parentPosition.x
          const moveY = moveE.pageY - parentPosition.y
          const skewVal = Math.sqrt(Math.pow(moveX - ex, 2) + Math.pow(moveY - ey, 2))
          const deg = Math.acos((moveX - ex) / skewVal) * 180 / Math.PI
          let offsetX = 0
          let offsetY = 0
          if (moveY - ey > 0) {
            offsetX = skewVal * Math.cos((deg - this.rotate) * Math.PI / 180)
            offsetY = skewVal * Math.sin((deg - this.rotate) * Math.PI / 180)
          } else {
            offsetX = skewVal * Math.cos((deg + this.rotate) * Math.PI / 180)
            offsetY = -skewVal * Math.sin((deg + this.rotate) * Math.PI / 180)
          }
          const hasT = /n/.test(cropperAction)
          const hasB = /s/.test(cropperAction)
          const hasL = /w/.test(cropperAction)
          const hasR = /e/.test(cropperAction)
          let newWidth = width + (hasL ? -offsetX : hasR ? offsetX : 0)
          let newHeight = height + (hasT ? -offsetY : hasB ? offsetY : 0)
          let newLeft = left + (hasL ? offsetX : 0)
          let newTop = top + (hasT ? offsetY : 0)
          if (cropperAction.length === 2) {
            const scaleVal = Math.abs(offsetX) < Math.abs(offsetY) ? Math.abs(offsetX) : Math.abs(offsetY)
            if (hasL && hasT) {
              newLeft = left + (offsetX > 0 ? scaleVal : -scaleVal)
              newTop = top + (offsetX > 0 ? scaleVal : -scaleVal)
              newWidth = width + (offsetX > 0 ? -scaleVal : scaleVal)
              newHeight = height + (offsetX > 0 ? -scaleVal : scaleVal)
            }
            if (hasL && hasB) {
              newLeft = left + (offsetX > 0 ? scaleVal : -scaleVal)
              newWidth = width + (offsetX > 0 ? -scaleVal : scaleVal)
              newHeight = height + (offsetX > 0 ? -scaleVal : scaleVal)
            }
            if (hasR && hasT) {
              newTop = top + (offsetX > 0 ? -scaleVal : scaleVal)
              newWidth = width + (offsetX > 0 ? scaleVal : -scaleVal)
              newHeight = height + (offsetX > 0 ? scaleVal : -scaleVal)
            }
            if (hasR && hasB) {
              newWidth = width + (offsetX > 0 ? scaleVal : -scaleVal)
              newHeight = height + (offsetX > 0 ? scaleVal : -scaleVal)
            }
          }
          newHeight = newHeight > 0 ? newHeight : 0
          newWidth = newWidth > 0 ? newWidth <= 750 ? newWidth : 750 : 0
          this.imgData.cropBox = {
            ...this.imgData.cropBox,
            width: newWidth,
            height: newHeight,
            left: newLeft,
            top: newTop
          }
          this.imgData.cropBoxImg = {
            left: cLeft - newLeft,
            top: cTop - newTop
          }
        }
        const up = () => {
          const { left, top, width, height } = this.imgData.cropBox
          const point = {
            left,
            top,
            width,
            height
          }
          const {
            left: cLeft,
            top: cTop,
            width: cWidth,
            height: cHeight
          } = this.imgData.canvas
          let newPoint = {
            left: cLeft,
            top: cTop,
            width: cWidth,
            height: cHeight
          }
          if (cWidth < width || cHeight < height) {
            newPoint = this.getNewCoordinate(point, oldPoint, this.imgData.canvas)
          } else {
            let newLeft = left >= cLeft ? cLeft : left
            let newTop = top >= cTop ? cTop : top
            newLeft = cLeft + cWidth >= left + width ? newLeft : left + width - cWidth
            newTop = cTop + cHeight >= top + height ? newTop : top + height - cHeight
            newPoint.left = newLeft
            newPoint.top = newTop
          }
          this.imgData.canvas.left = newPoint.left
          this.imgData.canvas.top = newPoint.top
          this.imgData.canvas.width = newPoint.width
          this.imgData.canvas.height = newPoint.height
          this.imgData.cropBoxImg.left = newPoint.left - left
          this.imgData.cropBoxImg.top = newPoint.top - top
          this.changeTooltip(this.rotate)
          document.removeEventListener('mousemove', move)
          document.removeEventListener('mouseup', up)
        }
        document.addEventListener('mousemove', move)
        document.addEventListener('mouseup', up)
      }
    },
    getNewCoordinate(point, oldPoint, canvasPoint) {
      const center = {
        x: point.width / 2,
        y: point.height / 2
      }
      const oldCenter = {
        x: oldPoint.width / 2,
        y: oldPoint.height / 2
      }
      const offsetX = Math.abs((center.x - oldCenter.x) * 2) - Math.abs(oldPoint.width - canvasPoint.width) > 0 ? Math.abs((center.x - oldCenter.x) * 2) - Math.abs(oldPoint.width - canvasPoint.width) : 0
      const offsetY = Math.abs((center.y - oldCenter.y) * 2) - Math.abs(oldPoint.height - canvasPoint.height) > 0 ? Math.abs((center.y - oldCenter.y) * 2) - Math.abs(oldPoint.height - canvasPoint.height) : 0
      const scaleX = offsetX / oldPoint.width
      const scaleY = offsetY / oldPoint.height
      const scale = scaleX >= scaleY ? scaleX : scaleY
      if (scaleX === scaleY) {
        return {
          left: point.left,
          top: point.top,
          width: point.width,
          height: point.height
        }
      } else {
        if (scaleX) {
          const scaleHeight = canvasPoint.height + canvasPoint.height * scale
          return {
            left: point.left,
            top: point.top + (point.height - scaleHeight) / 2,
            width: point.width,
            height: scaleHeight
          }
        }
        if (scaleY) {
          const scaleWidth = canvasPoint.width + canvasPoint.width * scale
          return {
            left: point.left + (point.width - scaleWidth) / 2,
            top: point.top,
            width: scaleWidth,
            height: point.height
          }
        }
      }
    },
    /**
     * 获取旋转指定角度后的rect
     * @param  {[type]} options rect
     * @param  {[type]} angle   旋转角度
     * @return {[type]}
     */
    transform(options, angle) {
      const x = options.x
      const y = options.y
      const width = options.width
      const height = options.height

      const r = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) / 2
      const a = Math.round(Math.atan(height / width) * 180 / Math.PI)
      const tlbra = 180 - angle - a
      const trbla = a - angle
      const ta = 90 - angle
      const ra = angle

      const halfWidth = width / 2
      const halfHeight = height / 2

      const middleX = x + halfWidth
      const middleY = y + halfHeight

      const topLeft = {
        x: middleX + r * Math.cos(tlbra * Math.PI / 180),
        y: middleY - r * Math.sin(tlbra * Math.PI / 180)
      }
      const top = {
        x: middleX + halfHeight * Math.cos(ta * Math.PI / 180),
        y: middleY - halfHeight * Math.sin(ta * Math.PI / 180)
      }
      const topRight = {
        x: middleX + r * Math.cos(trbla * Math.PI / 180),
        y: middleY - r * Math.sin(trbla * Math.PI / 180)
      }
      const right = {
        x: middleX + halfWidth * Math.cos(ra * Math.PI / 180),
        y: middleY + halfWidth * Math.sin(ra * Math.PI / 180)
      }
      const bottomRight = {
        x: middleX - r * Math.cos(tlbra * Math.PI / 180),
        y: middleY + r * Math.sin(tlbra * Math.PI / 180)
      }
      const bottom = {
        x: middleX - halfHeight * Math.sin(ra * Math.PI / 180),
        y: middleY + halfHeight * Math.cos(ra * Math.PI / 180)
      }
      const bottomLeft = {
        x: middleX - r * Math.cos(trbla * Math.PI / 180),
        y: middleY + r * Math.sin(trbla * Math.PI / 180)
      }
      const left = {
        x: middleX - halfWidth * Math.cos(ra * Math.PI / 180),
        y: middleY - halfWidth * Math.sin(ra * Math.PI / 180)
      }
      const minX = Math.min(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x)
      const maxX = Math.max(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x)
      const minY = Math.min(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y)
      const maxY = Math.max(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y)
      return {
        point: [topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left],
        width: maxX - minX,
        height: maxY - minY,
        left: minX,
        right: maxX,
        top: minY,
        bottom: maxY
      }
    }
  }

}
</script>

<style lang="scss" scoped>
.contain {
  direction: ltr;
  font-size: 0;
  line-height: 0;
  position: absolute;

  .wrap-box, .canvas {
    overflow: hidden;
  }

  .canvas-hide {
    display: block;
    height: 0;
    position: absolute;
    width: 0;
  }

  .move {
    cursor: move;
    pointer-events: none;
  }

  .modal {
    background-color: #000;
    opacity: 0.5;
  }

  .crop-box {
    .view-box {
      display: block;
      height: 100%;
      outline: 1px solid rgba(51, 153, 255, 0.75);
      overflow: hidden;
      width: 100%;
    }

    .dashed {
      border: 0 dashed #eee;
      display: block;
      opacity: 0.5;
      position: absolute;
      pointer-events: none;

      &.dashed-h {
        border-bottom-width: 1px;
        border-top-width: 1px;
        height: calc(100% / 3);
        left: 0;
        top: calc(100% / 3);
        width: 100%;
      }

      &.dashed-v {
        border-left-width: 1px;
        border-right-width: 1px;
        height: 100%;
        left: calc(100% / 3);
        top: 0;
        width: calc(100% / 3);
      }
    }

    .face, .line, .point {
      display: block;
      height: 100%;
      opacity: 0.1;
      position: absolute;
      width: 100%;
    }

    .face {
      background-color: #fff;
      left: 0;
      top: 0;
    }

    .line {
      background-color: #39f;

      &.line-e {
        cursor: ew-resize;
        right: -3px;
        top: 0;
        width: 5px
      }

      &.line-n {
        cursor: ns-resize;
        height: 5px;
        left: 0;
        top: -3px;
      }

      &.line-w {
        cursor: ew-resize;
        left: -3px;
        top: 0;
        width: 5px;
      }

      &.line-s {
        bottom: -3px;
        cursor: ns-resize;
        height: 5px;
        left: 0;
      }
    }

    .point {
      background-color: #39f;
      height: 5px;
      opacity: 0.75;
      width: 5px;

      &.point-e {
        cursor: ew-resize;
        margin-top: -3px;
        right: -3px;
        top: 50%;
      }

      &.point-n {
        cursor: ns-resize;
        left: 50%;
        margin-left: -3px;
        top: -3px;
      }

      &.point-w {
        cursor: ew-resize;
        left: -3px;
        margin-top: -3px;
        top: 50%;
      }

      &.point-s {
        bottom: -3px;
        cursor: s-resize;
        left: 50%;
        margin-left: -3px;
      }

      &.point-ne {
        cursor: nesw-resize;
        right: -3px;
        top: -3px;
      }

      &.point-nw {
        cursor: nwse-resize;
        left: -3px;
        top: -3px;
      }

      &.point-sw {
        bottom: -3px;
        cursor: nesw-resize;
        left: -3px;
      }

      &.point-se {
        bottom: -3px;
        cursor: nwse-resize;
        height: 5px;
        opacity: 1;
        right: -3px;
        width: 5px;
      }
    }
  }

  .wrap-box, .canvas, .drag-box, .crop-box, .modal {
    bottom: 0;
    left: 0;
    position: absolute;
    right: 0;
    top: 0;
  }

  img {
    display: block;
    image-orientation: 0deg;
    max-height: none !important;
    max-width: none !important;
    min-height: 0 !important;
    min-width: 0 !important;
    width: 100%;
    height: 100%;
  }
}

.tooltip {
  position: absolute;
  background: #fff;
  z-index: 9999;
  clear: both;
  border-radius: 3px;
  box-shadow: 0 0 5px rgba(0,0,0,.3);
  cursor: default;
  min-width: 85px;

  &-btn {
    display: inline-block;
    background: none;
    border: 0;
    border-radius: 3px;
    color: #333;
    cursor: pointer;
    line-height: 28px;
    outline: 0;
    position: relative;
    white-space: nowrap;
    vertical-align: top;
    text-align: center;
    -webkit-appearance: none;
    width: 40px;
    height: 28px;
    font-size: 12px;
  }
}
</style>
